package com.gitee.zjbqdzq.core;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.File;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class NnyyParser extends ResourceParser {
    private final ExecutorService executors = Executors.newVirtualThreadPerTaskExecutor();
    private final String baseUrl;
    private final String saveFolder;
    private final Pattern pattern = Pattern.compile("\"video_plays\": ?(\\[.+\\])", Pattern.DOTALL);
    private final AtomicInteger delay = new AtomicInteger(0);

    public NnyyParser(String url, String saveFolder) {
        super(url);
        this.baseUrl = "https://nnyy.in";
        this.saveFolder = saveFolder;
    }

    record Tmp<T>(HttpClient client, T data) {

        public <R> Tmp<R> then(R data) {
            return new Tmp<>(client, data);
        }
    }

    record Temp<T, D>(HttpClient client, T data, D data2) {
    }

    @Override
    public Flux<NnyyResource> parse() {
        return Flux.just(getHttpClient())
                .flatMap(this::sendRequest)
                .flatMap(NnyyParser::getElements)
                .flatMap(this::getExistsFile)
                .flatMap(v -> parallelFetchResource(v.data(), v.data2(), v.client()));
    }

    private Flux<NnyyResource> parallelFetchResource(Elements elements, Set<String> existsFile, HttpClient client) {
        return Flux.just(elements.toArray(Element[]::new))
                .map(this::getResource)
                .filter(v -> filterExistsFile(v, existsFile))
                .delayElements(Duration.ofMillis(400L))
                .map(v -> this.getNnyyResource(v, client).join());
    }

    private HttpClient getHttpClient() {
        return HttpClient.newBuilder().connectTimeout(Duration.ofDays(1)).executor(executors).build();
    }

    private CompletableFuture<NnyyResource> getNnyyResource(Resource v, HttpClient client) {
        return readFromUrl(client, v.url, v.filename);
    }

    private static boolean filterExistsFile(Resource v, Set<String> existsFile) {
        boolean b = existsFile.contains(v.filename + ".mp4");
        if (b) {
            log.info("已经存在的文件：{}", v.filename);
        }
        return !b;
    }

    private Mono<Temp<Elements, Set<String>>> getExistsFile(Tmp<Elements> data) {
        return Mono.just(Paths.get(saveFolder))
                .map(Path::toFile)
                .mapNotNull(File::list)
                .switchIfEmpty(Mono.just(new String[0]))
                .map(Arrays::stream)
                .map(stream -> new Temp<>(data.client(), data.data(), stream.collect(Collectors.toSet())));
    }

    private static Mono<Tmp<Elements>> getElements(Tmp<String> bodyTmp) {
        return Mono.create(emit -> {
            Element body = Jsoup.parse(bodyTmp.data()).body();
            Elements elements = body.select("#eps-ul .play-btn");
            emit.success(bodyTmp.then(elements));
        });
    }

    private Resource getResource(Element v) {
        String urlSuffix = v.attr("ep_slug");
        String fileName = v.text();
        String url = String.join("/", baseUrl, "_gp", getId(), urlSuffix);
        return new Resource(url, fileName);
    }

    @Override
    public boolean supported() {
        return url.startsWith(this.baseUrl);
    }

    private String getId() {
        return url.substring(url.lastIndexOf("/") + 1, url.lastIndexOf(".html"));
    }

    private CompletableFuture<NnyyResource> readFromUrl(HttpClient client, String url, String fileName) {
        return readFromUrl(client, url, fileName, null);
    }

    private CompletableFuture<NnyyResource> readFromUrl(HttpClient client, String url, String fileName, Integer delay) {
        log.info("[{}]开始解析：{}", fileName, url);
        if (Objects.isNull(delay)) {
            return fetchResource(client, url)
                    .thenCompose(response -> parseResponse(client, url, fileName, response));
        } else {
            log.warn("由于请求过于频繁，所以延迟[{}]毫秒执行", delay);
            return CompletableFuture.runAsync(() -> {
                    }, CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS))
                    .thenCompose((ignore) -> fetchResource(client, url))
                    .thenCompose((response) -> parseResponse(client, url, fileName, response));
        }
    }

    private CompletableFuture<NnyyResource> parseResponse(HttpClient client, String url, String fileName, HttpResponse<String> response) {
        if (HttpStatus.HTTP_OK == response.statusCode()) {
            String body = response.body();
            Matcher matcher = pattern.matcher(body);
            if (!matcher.find()) {
                log.error("未从响应体中解析到资源:{}", body);
                return CompletableFuture.completedFuture(null);
            }
            return CompletableFuture.supplyAsync(() -> {
                JSONArray objects = JSONUtil.parseArray(matcher.group(1));
                Map<String, String> sources = separateMyNeedData(objects);
                return new NnyyResource(sources, fileName);
            }, executors);
        } else {
            return readFromUrl(client, url, fileName, 200);
        }
    }

    private String parseBody(HttpResponse<String> response) {
        String body = response.body();
        if (StrUtil.isBlank(body)) {
            log.error("响应体为空, 响应码为：{}", response.statusCode());
        }
        return body;
    }

    private static Map<String, String> separateMyNeedData(JSONArray objects) {
        return objects.stream().map(JSONUtil::parseObj).collect(Collectors.toMap(v -> v.getStr("src_site"), v -> v.getStr("play_data"), (a, b) -> a));
    }

    private static CompletableFuture<HttpResponse<String>> fetchResource(HttpClient client, String url) {
        HttpRequest request = buildResourceRequest(url);
        return client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
    }

    private static HttpRequest buildResourceRequest(String url) {
        return HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
    }

    private Mono<Tmp<String>> sendRequest(HttpClient client) {
        return Mono.create(emit -> {
            HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create(url)).header("Content-Type", "text/html; charset=utf-8").build();
            HttpResponse.BodyHandler<String> stringBodyHandler = HttpResponse.BodyHandlers.ofString();
            client.sendAsync(request, stringBodyHandler)
                    .thenAccept(response -> {
                        if (HttpStatus.HTTP_OK == response.statusCode()) {
                            emit.success(new Tmp<>(client, response.body()));
                        } else {
                            emit.error(new RuntimeException("获取html异常" + response.statusCode()));
                        }
                    });
        });
    }

    record Resource(String url, String filename) {
    }
}
